/*
 * rsidlib: Open source library for PKI functions on Serbian eID (GNU LGPLv3)
 * Copyright (C) 2011 Aleksandar Nikolic
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see
 * http://www.gnu.org/licenses/.
 */

package rsidlib;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;


/**
 * Klasa za rukovanje elektronskom licnom kartom.
 * @author Aleksandar Nikolic
 *
 */
@SuppressWarnings("restriction")
public class RSIDCard {

	/**
	 * Terminal na kom se nalazi elektronska licna  karta
	 */
	private CardTerminal terminal;
	private Card card = null;
	private CardChannel channel = null;
	
	
	/**
	 * Podaci o dokumentu
	 */
    private static byte[] DOCUMENT_FILE  = {0x0F, 0x02}; 
    /**
     *  Licni podaci
     */
    private static byte[] PERSONAL_FILE  = {0x0F, 0x03}; 
    /**
     * Podaci o mestu prebivalista
     */
    private static byte[] RESIDENCE_FILE = {0x0F, 0x04}; // Podaci o mestu prebivalista
    /**
     * Licna slika u JPEG formatu
     */
    private static byte[] PHOTO_FILE     = {0x0F, 0x06}; // Personal photo in JPEG format
    /**
     * Kvalifikovani javni sertifikat 
     */
    private static byte[] QUALIFIED_CERTIFICATE = {0x0F, 0x10}; // Public X.509 certificate for qualified (Non Repudiation) signing
    /**
     * Nekvalifikovani, standardni, sertifikat
     */
    private static byte[] STANDARD_CERTIFICATE  = {0x0F, 0x08}; // Public X.509 certificate for standard signing
    private static byte[] FIRST_UNKNOWN = {0x0F, (byte) 0xA3}; // cita se pre VERIFY 
    /**
     * Enkriptovani pin i tajni kljuc za dekriptovanje privatnog kljuca
     */
    private static byte[] ENCRYPTED_PIN = {0x0F, 0x13}; // drugi se cita ppre VERIFY
    private static byte[] XOR_VALUE = {0x0F, (byte) 0xA1}; // treci se cita pre VERIFY
    /**
     * Podaci privatnog kljuca
     */
    public byte[] PRIVATE_KEY = {0x0F, 0x09}; // unknown , selektuje ga pri potpisivanju
	
    private static final int BLOCK_SIZE = 0xFF;
	
    /**
     * Konstruise RSIDCard objekat vezan za dati terminal
     * @param terminal
     */
    public RSIDCard(CardTerminal terminal){
		this.terminal = terminal;
    }
    
    /**
     * Povezivanje sa karticom
     * @throws CardException
     */
    private void connect() throws CardException
    {
    	card = terminal.connect("*");
    	channel = card.getBasicChannel();
    }
    
    /**
     * Kraj rada sa karticom
     * @throws CardException
     */
    private void disconnect() throws CardException
    {
    	card.disconnect(false);
    	card = null;
    }
    
    /**
     * Selektovanje elementarnog fajla po oznaci 
     * @param name
     * @throws CardException
     */
    private void selectFile(byte[] name) throws CardException
    {
		ResponseAPDU r = channel.transmit(new CommandAPDU(0x00, 0xA4, 0x08, 0x00, name));
		if(r.getSW() != 0x9000) {
			throw new CardException("Select failed: " + RSIDUtils.int2Hex(r.getSW()));
		}
    }
    
    /**
     * Citanje dela selektovanog elementarnog fajla
     * @param offset
     * @param length
     * @return niz procitanih bajtova
     * @throws CardException
     */
    private byte[] readBinary(int offset, int length) throws CardException
    {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        while(length > 0)
        {
        	int block = Math.min(length, BLOCK_SIZE);
            ResponseAPDU r = channel.transmit(new CommandAPDU(0x00, 0xB0, offset >> 8, offset & 0xFF, block));
            if(r.getSW() != 0x9000) {
            	throw new CardException("Read binary failed: " + RSIDUtils.int2Hex(r.getSW()));
            }

            try {
                byte[] data = r.getData();
                int data_len = data.length;

                out.write(data);
	            offset += data_len;
	            length -= data_len;
			} catch (IOException e) {
				throw new CardException("Read binary failed: Could not write byte stream");
			}
        }

        return out.toByteArray();
    }

    /**
     * Citanje celog elementarnog fajla
     * @param name
     * @return
     * @throws CardException
     */
    private byte[] readElementaryFile(byte[] name) throws CardException
    {
    	selectFile(name);

    	// Read first 6 bytes from the EF
		byte[] header = readBinary(0, 6);

		// Missing files have header filled with 0xFF
		int i = 0;
		while(i < header.length && header[i] == 0xFF) i++;
		if(i == header.length) {
			throw new CardException("Read EF file failed: File header is missing");
		}
		
		// Total EF length: data as 16bit LE at 4B offset
		int length = ((0xFF&header[5])<<8) + (0xFF&header[4]);
    	
		// Read binary into buffer
		return readBinary(6, length);
    }
    
    
    /**
     * Potpisivanje prosledjenih podataka standardnim privatnim kljucem sa 
     * elektronske licne karte gradjana.
     * Koraci:
     * 1. Dekriptovanje PINa za pristup kartici i tajnog kljuca za dekripciju privatnog kljuca
     * 2. Autorizacija korisnika ekriptovanim PINom
     * 3. Citanje privatnog kljuca sa kartice
     * 4. Potpisivanje podataka
     * 
     * @param data - podaci koji se potpisuju
     * @param password - lozinka kartice
     * @return
     * @throws RSIDCardException 
     */
    public byte[] SignData(byte[] data, String password) throws RSIDCardException{
    	
    	// prvi korak : dekriptovanje pina i tajnog kljuca
    	String id; 
    	byte[] signedData  = null;
    	HashMap<Integer, byte[]> document = null;
    	try {
			connect();
		} catch (CardException e2) {
			throw new RSIDCardException("Greska pri povezivanju na karticu");
		}
		try {
			document = RSIDUtils.parseTLV(readElementaryFile(DOCUMENT_FILE));
		} catch (CardException e) {
			System.out.println("Could not parse DOCUMENT_FILE" + e.getMessage());
		}
		if(document == null){
			throw new RSIDCardException("Greska pri citanju podataka o licnoj karti");
		}
    	id = RSIDUtils.bytes2UTFString(document.get(10));
    	
    	/* SHA1 hash vrednost xorKey ucestvuje u dekripciji PINa za pristup kartici i
    	 * dekripciji tajnog kljuca za dekriptovanje privatnog kljuca. 
    	 */
   		String xorKey =  "ID" + id + '\u0001' + password;
   		byte[] xorKeyBytes = xorKey.getBytes();
   		
   	    MessageDigest md = null;
   	    try {
   			md = MessageDigest.getInstance("SHA-1");
   		} catch (NoSuchAlgorithmException e) {
   			e.printStackTrace();
   		}
   		
   	    byte[] xorKeyHash;
   	    md.update(xorKeyBytes, 0, xorKeyBytes.length);
   	    xorKeyHash = md.digest();
   	    byte[] encryptedPinAndSecretKey = null;
   	    /*
   	     * ENCRYPTED_PIN elementarni fajl sadrzi enkriptovani PIN duzine 8 fajta pocevsi od cetvrtog bajta.
   	     * Na ofsetu 17 se nalazi tajni kljuc za dekriptovanje privatnog kljuca. Duzina tajnog kljuca je 32 bajta.
   	     * ----------------------------------------------------------------------------------------------------
   	     * |tlv tag(4 bajta)| enkriptovani pin (8 bajta)|tlv tag (4 bajta)| enkriptovani tajni kljuc (32 bajta)|
   	     * ---------------------------------------------------------------------------------------------------- 
   	     */
   	    try {
			encryptedPinAndSecretKey = readElementaryFile(ENCRYPTED_PIN);
		} catch (CardException e) {
			throw new RSIDCardException("Greska pri citanju enkriptovanig pina i tajnog kljuca.");
		}
		
		// enkriptvotani pin
		byte[] encryptedPin = new byte[8];
		// enkriptovatni tajni kljuc
		byte[] encryptedSecretKey = new byte[32];
		
		System.arraycopy(encryptedPinAndSecretKey, 4, encryptedPin, 0, 8);
		System.arraycopy(encryptedPinAndSecretKey, 17, encryptedSecretKey, 0, 32);
		
		byte[] xorValueWhole = null;
		try {
			xorValueWhole = readElementaryFile(XOR_VALUE);
		} catch (CardException e) {
			throw new RSIDCardException("Greska pri citanju podataka za dekriptovanje PINa!");
		}
    	
		// dekriptovani pin
		byte[] pin = rsidXorDecryptPIN(encryptedPin, xorKeyHash, xorValueWhole);
		// dekriptovani tajni kljuc
		byte[] secretKey = rsidXorDecryptSecretKey(encryptedSecretKey, xorKeyHash, xorValueWhole);
		// drugi korak: autorizacija korisnika 
		
		verifyUser(pin);
		// treci korak: citanje i dekripcija privatnog kljuca 
		RSAPrivateCrtKeySpec privateKeySpec = getPrivateKey(secretKey);
		// cetvrti korak: potpisivanje podataka
        KeyFactory factory;

        try {
            factory = KeyFactory.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        
        PrivateKey privateKey = null;
		try {
			privateKey = factory.generatePrivate(privateKeySpec);
		} catch (InvalidKeySpecException e1) {
			e1.printStackTrace();
		}
        
		if(privateKey == null){
			throw new RSIDCardException("Greska pri generisanju privatnog kljuca!");
		}
		
		Signature sig;
		try {
			sig = Signature.getInstance("SHA1withRSA");
			//inicijalizacija privatnim kljucem 
			sig.initSign(privateKey);
			//postavljamo podatke koje potpisujemo
			sig.update(data);
			
			signedData = sig.sign();

		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (SignatureException e) {
			e.printStackTrace();
		}  
    	if(signedData == null){
    		throw new RSIDCardException("Greska pri potpisivanju podataka!");
    	}
    	try {
			disconnect();
		} catch (CardException e) {
			throw new RSIDCardException("Greska pri oslobadjanju kartice.");
		}
    	return signedData;
    }

    public boolean verifySignature(byte[] signature, byte[] data) throws RSIDCardException{
    	
    	try {
			connect();
		} catch (CardException e2) {
			throw new RSIDCardException("Greska pri povezivanju na karticu");
		}
		// citamo sertifikat zbog podataka o javnom kljucu
		byte[] standardFileBytes;
		try {
			standardFileBytes = readElementaryFile(STANDARD_CERTIFICATE);
		} catch (CardException e) {
			throw new RSIDCardException("Greska pri citanju standardnog sertifikata!");
		}
		byte[] certificateBytes = new byte[standardFileBytes.length];
		System.arraycopy(standardFileBytes, 4, certificateBytes, 0, standardFileBytes.length-4);
		ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(certificateBytes);
		CertificateFactory cf = null;
		try {
			cf = CertificateFactory.getInstance("X.509");
		} catch (CertificateException e) {
			e.printStackTrace();
		}
		X509Certificate x509Cert = null;
		try {
			x509Cert = (X509Certificate) cf.generateCertificate(certificateInputStream);
		} catch (CertificateException e) {
			e.printStackTrace();
		}
		
		// javni kljuc se sastoji od modula i javnog eksponenta u odgovarajucoj ASN1 strukturi
		ASN1InputStream is = new ASN1InputStream(x509Cert.getPublicKey().getEncoded());
	    DERObject obj = null;
		try {
			obj = is.readObject();
		} catch (IOException e) {
			e.printStackTrace();
		}
	    ASN1Sequence seq = (ASN1Sequence)obj;
	    RSAPublicKeyStructure pubk = null;
	    SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(seq);
	    try {
			 pubk = RSAPublicKeyStructure.getInstance(pkInfo.getPublicKey());
		} catch (IOException e) {
			e.printStackTrace();
		}

		RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(pubk.getModulus(), pubk.getPublicExponent());
        KeyFactory factory;

        try {
            factory = KeyFactory.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        
		PublicKey publicKey = null;
		try {
			publicKey = factory.generatePublic(rsaPubKey);
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		}
		Signature sig = null;
		try {
			sig = Signature.getInstance("SHA1withRSA");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}    
		try {
			sig.initVerify(publicKey);
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		}
		try {
			sig.update(data);
		} catch (SignatureException e) {
			e.printStackTrace();
		}
				
		boolean res = false;
		try {
			res = sig.verify(signature);
		} catch (SignatureException e) {
			e.printStackTrace();
		}
		try {
			disconnect();
		} catch (CardException e) {
			e.printStackTrace();
		}
    	return res;
    }
    /**
     * Dekripcija i rekonstrukcija privatnog kljuca.
     * Privatni kljuc je enkriptovan AESom sa tajnim kljucem od 256 bita u CFB modu.
     * Inicijalizacioni vektor za CFB mod su prvih 16 bajta SHA1 hasha tajnog kljuca
     * U dekriptovanim podacima privatni kljuc se nalazi u ASN1 strukturi nakon TLV 
     * polja od 4 bajta. ASN1 struktura sadrzi prvi prosti faktor, drugi prosti faktor, 
     * eksponent prvog prostog faktora, eksponent drugog prostog faktora i koeficijent
     * za teoremu o kineskom ostatku.
     * @param secretKey
     * @return
     * @throws RSIDCardException 
     */
    private RSAPrivateCrtKeySpec getPrivateKey(byte[] secretKey) throws RSIDCardException{

    	// Enkriptovani privatni kljuc
    	byte[] encryptedPrivateKey = null;
    	try {
			encryptedPrivateKey = readElementaryFile(PRIVATE_KEY);
		} catch (CardException e) {
			throw new RSIDCardException("Greska pri citanju enkriptovanog privatnog kljuca.");
		}
		
		// Privatni kljuc je enkriptovan AESom sa tajnim kljucem od 256 bita u CFB modu 
        Security.addProvider(new BouncyCastleProvider());
        SecretKeySpec                     key;
        Cipher                  cipher = null;
        
        // zbog CFB moda potreban nam je IV
        byte[] iv = new byte[16];

        //Inicijalizacioni vektor za CFB mod su prvih 16 bajta SHA1 hasha tajnog kljuca
        MessageDigest md = null;
        try {
    		md = MessageDigest.getInstance("SHA-1");
    	} catch (NoSuchAlgorithmException e) {
    		e.printStackTrace();
    	}
        byte[] sha1hash = new byte[40];
        md.update(secretKey, 0, secretKey.length);
        sha1hash = md.digest();
        System.arraycopy(sha1hash, 0, iv, 0, 16);

        //postavljanje IVa
        IvParameterSpec ivSpec = new IvParameterSpec(iv);

        // Postavljanje tajnog kljuca za dekripciju
        key = new SecretKeySpec(secretKey, "AES");
        
        // Instanciranje AES sifrara u CFB modu 
        try {
			cipher = Cipher.getInstance("AES/CFB/NoPadding", "BC");
		} catch (NoSuchAlgorithmException e1) {
			e1.printStackTrace();
		} catch (NoSuchProviderException e1) {
			e1.printStackTrace();
		} catch (NoSuchPaddingException e1) {
			e1.printStackTrace();
		}
		
		// inicijalizacija AESa tajnim kljucem i inicijalizacionim vektorom
        try {
			cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (InvalidAlgorithmParameterException e) {
			e.printStackTrace();
		}
		
		// DEKRIPCIJA
        byte[] plainText = new byte[cipher.getOutputSize(encryptedPrivateKey.length)];

        int ptLength = 0;
		try {
			ptLength = cipher.update(encryptedPrivateKey, 0, encryptedPrivateKey.length, plainText, 0);
		} catch (ShortBufferException e) {
			e.printStackTrace();
		}
        
        try {
			ptLength += cipher.doFinal(plainText, ptLength);
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (ShortBufferException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
		// u dekriptovanim podacima privatni kljuc se nalazi u ASN1 strukturi nakon TLV polja od 4 bajta

		byte[] decryptedPrivateKey = new byte[plainText.length];
		System.arraycopy(plainText, 4, decryptedPrivateKey, 0, plainText.length-4);
        
		RSAPrivateCrtKeySpec privKeySpec = reconstructPrivateKey(decryptedPrivateKey);
		if(privKeySpec == null){
			throw new RSIDCardException("Greska pri rekonstrukciji privatnog kljuca.");
		}
		
		return privKeySpec;
    }
    
    /**
     * Rekonstrukcija privatnog kljuca iz dekriptovanih podataka.
     * Podaci se nalaze unutar sledece ASN1 sturkture:
     * PrivateKey ::= SEQUENCE {
	 *			primeP BITSTRING,
	 *			primeQ BITSTRING,
	 *			primeExponentP BITSTRING,
	 *			primeExponentQ BITSTRING,
	 * 			crtCoeficient  BITSTRING
	 *	}
	 * Privatni kljuc je u obliku spremnom za potpisivanje koriscenjem teoreme 
	 * kineskog ostatka, stoga privatni eksponent nije potreban. 
     * @param privateKey
     * @return
     */
	private RSAPrivateCrtKeySpec reconstructPrivateKey(byte[] privateKey){
		
		RSAPrivateCrtKeySpec privKeySpec = null;
		ASN1InputStream asn1InputStream = new ASN1InputStream(privateKey);
		//parsiranje ASN1 strukture
		ArrayList<BigInteger> privateKeyParts = new ArrayList<BigInteger>();
		try {
			DERObject derObject = asn1InputStream.readObject();
			ASN1Sequence asn1Seq = (ASN1Sequence) derObject;
			
			Enumeration<DERObject> en = asn1Seq.getObjects();
			while(en.hasMoreElements()){
				DERObject obj = en.nextElement();
				DERBitString bitString = DERBitString.getInstance(obj);
				byte[] prependZero = new byte[bitString.getBytes().length+1];
				// zbog "cudnog" BigInteger konstruktora mora se dodati 0x0 na pocetak,
				// u suprotnom u pojedinim slucajevima pogresno se protumaci znak i brojevi ispadnu negativni
				prependZero[0] = 0x0;
				System.arraycopy(bitString.getBytes(), 0, prependZero, 1, bitString.getBytes().length);
				privateKeyParts.add(new BigInteger(prependZero));
			}

			// citamo sertifikat zbog podataka o javnom kljucu
			byte[] standardFileBytes = readElementaryFile(STANDARD_CERTIFICATE);
			byte[] certificateBytes = new byte[standardFileBytes.length];
			System.arraycopy(standardFileBytes, 4, certificateBytes, 0, standardFileBytes.length-4);
			ByteArrayInputStream certificateInputStream = new ByteArrayInputStream(certificateBytes);
			CertificateFactory cf = CertificateFactory.getInstance("X.509");
			X509Certificate x509Cert = (X509Certificate) cf.generateCertificate(certificateInputStream);
			
			// javni kljuc se sastoji od modula i javnog eksponenta u odgovarajucoj ASN1 strukturi
			ASN1InputStream is = new ASN1InputStream(x509Cert.getPublicKey().getEncoded());
		    DERObject obj = is.readObject();
		    ASN1Sequence seq = (ASN1Sequence)obj;
		    SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(seq);
		    RSAPublicKeyStructure pubk = RSAPublicKeyStructure.getInstance(pkInfo.getPublicKey());
		    BigInteger expo = pubk.getPublicExponent();
		    BigInteger mod = pubk.getModulus(); 
		    
		    // sada imamo sve potrebne podatke za privatni kljuc
			privKeySpec = new RSAPrivateCrtKeySpec(mod,
													expo,
													null,
													privateKeyParts.get(0),
													privateKeyParts.get(1),
													privateKeyParts.get(2),
													privateKeyParts.get(3),
													privateKeyParts.get(4));
		} catch (IOException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (CardException e) {
			e.printStackTrace();
		}
		
		return privKeySpec;
	}
    /**
     * Verifikacija korisnika PINom pomocu VERIFY APDU komande 
     * {@link http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_6_basic_interindustry_commands.aspx#chap6_12}
     * @param pin
     * @throws RSIDCardException
     */
    private void verifyUser(byte[] pin) throws RSIDCardException{
        
    	final byte CLA = 0x00;
        final byte INS = 0x20;
        final byte P1  = 0x00;
        final byte P2 =  0x01;
        final byte LC  = 0x08;
    	byte[] cmd = new byte[14];
    	
    	cmd[0] = CLA;
    	cmd[1] = INS;
    	cmd[2] = P1;
    	cmd[3] = P2;
    	cmd[4] = LC;
    	System.arraycopy(pin, 0, cmd, 5, 8);
    	cmd[13] =  0x00;
    	ResponseAPDU r = null;
		try {
			r = channel.transmit(new CommandAPDU(cmd));
		} catch (CardException e) {
			throw new RSIDCardException("Greska pri autorizaciji korisnika. Pogresna loznika?");
		}
		if(r.getSW() != 0x9000) {
			throw new RSIDCardException("Greska pri autorizaciji korisnika. Pogresna loznika?");
		}
    }
    
    /**
 	 *Dekriptovanje podataka se vrsi u dva koraka:
	 * 1. Enkriptovani podaci (encryptedData) se XORuju sa xorValue
	 * 2. Rezultat se XORuje sa xorKeyHash, tj hash vrednosti xorKey-a
	 * u koji ulazi korisnikova lozinka.
	 * Rezultat drugog koraka su dekriptovani dekriptovani pocaci.
	 * U slucaju da su podaci duzi od kljuca za XOR, kljuc se koristi ciklicno.
     * @param encryptedData  - enkriptovani pin
     * @param xorKeyHash - SHA1 hash stringa u koji ulazi lozinka 
     * @param xorValueWhole - XOR_VALUE podaci
     * @return - dekriptovani podaci
     */
    private byte[] rsidXorDecryptPIN(byte[] encryptedPIN, byte[] xorKeyHash, byte[] xorValueWhole){
		// ucestvuje kao XOR kljuc u prvom delu dekriptovanja PINa i kljuca
		byte[] xorValue = new byte[xorValueWhole.length];
		System.arraycopy(xorValueWhole, 4, xorValue, 0,8);

		/* dekriptovanje PINa se vrsi u dva koraka:
		 * 1. Enkriptovani pin (encryptedPin) se XORuje sa xorValue
		 * 2. Rezultat se XORuje sa prvih 8 bajta xorKeyHash, tj hash vrednosti xorKey-a
		 * u koji ulazi korisnikova lozinka.
		 * Rezultat drugog koraka je dekriptovani PIN.
		 */
		
		// prvi korak
		byte[] xoredPIN = new byte[8];
		for(int i = 0; i<8;i++){
			xoredPIN[i] = (byte) (xorValue[i] ^ encryptedPIN[i]);
		}
		
		//drugi korak 
		// nakon ovoga imamo dekriptovani PIN
		byte[] decryptedData = new byte[xoredPIN.length]; 
		for(int i = 0 ; i<8; i++){
			
			decryptedData[i] = (byte) (xoredPIN[i] ^ xorKeyHash[i]);
		}
    	return decryptedData;
    }
    
    /**
     * Slicno kao rsidXorDecryptPIN samo umesto 8 radi sa 16 bajta
     * @param encryptedSecretKey
     * @param xorKeyHash
     * @param xorValueWhole
     * @return
     */
    private byte[] rsidXorDecryptSecretKey(byte[] encryptedSecretKey, byte[] xorKeyHash, byte[] xorValueWhole){
		// ucestvuje kao XOR kljuc u prvom delu dekriptovanja PINa i kljuca
		byte[] xorValue = new byte[xorValueWhole.length];
		System.arraycopy(xorValueWhole, 4, xorValue, 0, 16);

		/* dekriptovanje PINa se vrsi u dva koraka:
		 * 1. Enkriptovani pin (encryptedPin) se XORuje sa xorValue
		 * 2. Rezultat se XORuje sa prvih 8 bajta xorKeyHash, tj hash vrednosti xorKey-a
		 * u koji ulazi korisnikova lozinka.
		 * Rezultat drugog koraka je dekriptovani PIN.
		 */
		
		// priv korak
		byte[] xoredPIN = new byte[32];
		for(int i = 0; i<32;i++){
			xoredPIN[i] = (byte) (xorValue[i%16] ^ encryptedSecretKey[i]);
		}
		
		//drugi korak 
		// nakon ovoga imamo dekriptovani PIN
		byte[] decryptedData = new byte[32]; 
		for(int i = 0 ; i<32; i++){
			
			decryptedData[i] = (byte) (xoredPIN[i] ^ xorKeyHash[i%16]);
		}
    	return decryptedData;
    }
    /**
     * Test metoda
     * @param args
     */
    public static void main(String[] args){
    	
    	if(args.length != 1){
    		System.out.println("Lozinka mora biti prvi argument!");
    		System.exit(0);
    	}
    	String password =  args[0];
    	TerminalFactory factory = TerminalFactory.getDefault();
        List<CardTerminal> terminals = null;
		try {
			terminals = factory.terminals().list();
		} catch (CardException e) {
			e.printStackTrace();
		}
        CardTerminal terminal = terminals.get(0);
    	RSIDCard rsidCard = new RSIDCard(terminal);
    	
		byte[] dataToSign = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};
		byte[] signedData = null;
		try {
			signedData = rsidCard.SignData(dataToSign, password);
		} catch (RSIDCardException e) {
			System.out.println(e.getMessage());
			e.printStackTrace();
			System.exit(0);
		}
		System.out.println("Data to sign: " + RSIDUtils.bytes2Hex(dataToSign));
		System.out.println("Signed data: " + RSIDUtils.bytes2Hex(signedData));
		try {
			System.out.println("Verifying signature... " + rsidCard.verifySignature(signedData, dataToSign) );
		} catch (RSIDCardException e) {
			System.out.println(e.getMessage());
			e.printStackTrace();
			System.exit(0);
		}
    }
 
}


@SuppressWarnings("serial")
class RSIDCardException extends Exception{
	
	public RSIDCardException(String msg) {
		super(msg);
	}
}


